/*
 * Copyright (C) 2020 Agrui
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ParameterCheck.h"
#include "OSMemory.h"
#include "ArrayList.h"
#include "IpcServiceManager.h"
#include "IpcChannelManager.h"
#include "IpcType.h"
#include "IpcMgrService.h"

static IpcServiceManager* serviceManager = NULL;

static void onChannelDataReceive(void* callbackData, int id, void* data, int dataLen) {
    IpcServiceManager* serviceManager = (IpcServiceManager*)callbackData;
    dispatchServiceRequest(serviceManager, id, data, dataLen);
}

static void onChannelStateChanged(void* callbackData, int id, int state) {
    IpcServiceManager* serviceManager = (IpcServiceManager*)callbackData;
    if (state == IPC_STATE_DISCONNECTED) {
        dispatchClientDisconnected(serviceManager, id);
    } else if (state == IPC_STATE_CONNECTED) {

    }
}

IpcServiceManager* newIpcServiceManager() {
    IpcServiceManager* manager = OS_ALLOC_AND_RESET(IpcServiceManager);
    PARAMTER_CHECK_RETURN_NULL(manager);
    manager->list = newArrayList();
    if (!manager->list) {
        OS_FREE(manager);
        return NULL;
    }
    serviceManager = manager;
    addService(manager, newIpcMgrService());

    IpcChannelManager* channelManager = getDefaultIpcChannelManager();

    registerIpcChannelStateChanged(channelManager, 0, onChannelStateChanged, manager);
    registerIpcChannelStateChanged(channelManager, 1, onChannelStateChanged, manager);

    registerIpcChannelDataReceiveInd(channelManager, 0, onChannelDataReceive, manager);
    registerIpcChannelDataReceiveInd(channelManager, 1, onChannelDataReceive, manager);

    return manager;
}

static void freeService(void* userData) {
    IpcService* service = (IpcService*)userData;
    if (service) {
        deleteIpcService(service);
    }
}

void deleteIpcServiceManager(IpcServiceManager* manager) {
    if (manager) {
        if (manager->list) {
            arrayListRemoveAll(manager->list, freeService);
            deleteArrayList(manager->list);
        }
        OS_FREE(manager);
    }
    serviceManager = NULL;
}


IpcServiceManager* getDefaultIpcServiceManager() {
    return serviceManager;
}

void addService(IpcServiceManager* manager, IpcService* service) {
    PARAMTER_CHECK_RETURN_VOID(manager && manager->list && service);
    arrayListAddKey(manager->list, service->serviceId, service);
}

static IpcService* getService(IpcServiceManager* manager, int serviceId) {
    PARAMTER_CHECK_RETURN_NULL(manager && manager->list);
    return (IpcService*)arrayListGetKeyData(manager->list, serviceId);
}

IPC_RESULT sendIpcResponse(IpcTransaction* transaction, IPC_RESULT result, void* data, int dataLen) {
    IpcChannelManager* channelManager = (IpcChannelManager*)getDefaultIpcChannelManager();
    if (channelManager) {
        IpcChannel* channel = getChannel(channelManager, transaction->channelId);
        if (channel) {
            int bufferLen = sizeof(IpcDataHeadInfo) + dataLen;
            int8_t* buffer = (int8_t*)OS_MALLOC(bufferLen);
            PARAMTER_CHECK_RETURN_VALUE(buffer, IPC_RESULT_OUT_OF_MEMORY);
            IpcDataHeadInfo* header = (IpcDataHeadInfo*)buffer;
            header->type = IPC_TYPE_RESPONSE;
            header->transaction = transaction->transaction;
            header->serviceId = transaction->serviceId;
            header->msgId = transaction->msgId;
            header->resultCode = result;
            header->dataLen = dataLen;
            int8_t* temp = buffer + sizeof(IpcDataHeadInfo);
            if (data && dataLen > 0) {
                memcpy(temp, data, dataLen);
                bufferLen += dataLen;
            }
            channel->sendData(channel, buffer, bufferLen);
            OS_FREE(buffer);
            OS_FREE(transaction);
            return IPC_RESULT_OK;
        }
    }
    OS_FREE(transaction);
    return IPC_RESULT_ERROR;
}

IPC_RESULT sendIpcIndication(IpcService* service, int channelId, int msgId, void* data, int dataLen) {
    IpcChannelManager* channelManager = (IpcChannelManager*)getDefaultIpcChannelManager();
    if (channelManager) {
        IpcChannel* channel = getChannel(channelManager, channelId);
        if (channel) {
            int bufferLen = sizeof(IpcDataHeadInfo) + dataLen;
            int8_t* buffer = (int8_t*)OS_MALLOC(bufferLen);
            PARAMTER_CHECK_RETURN_VALUE(buffer, IPC_RESULT_OUT_OF_MEMORY);
            IpcDataHeadInfo* header = (IpcDataHeadInfo*)buffer;
            header->type = IPC_TYPE_INDICATION;
            header->transaction = -1;
            header->serviceId = service->serviceId;
            header->msgId = msgId;
            header->resultCode = 0;
            header->dataLen = dataLen;
            int8_t* temp = buffer + sizeof(IpcDataHeadInfo);
            if (data && dataLen > 0) {
                memcpy(temp, data, dataLen);
                bufferLen += dataLen;
            }
            channel->sendData(channel, buffer, bufferLen);
            OS_FREE(buffer);
            return IPC_RESULT_OK;
        }
    }
    return IPC_RESULT_ERROR;
}

IPC_RESULT dispatchServiceRequest(IpcServiceManager* manager, int channelId, void* data, int dataLen) {
    IpcHeadInfo* ipcHeader = (IpcHeadInfo*)data;
    IpcService* service = getService(manager, ipcHeader->serviceId);
    IpcTransaction* transaction = OS_ALLOC_AND_RESET(IpcTransaction);
    if (service && transaction) {
        transaction->channelId = channelId;
        transaction->transaction = ipcHeader->transaction;
        transaction->serviceId = ipcHeader->serviceId;
        transaction->clientId = ipcHeader->clientId;
        transaction->msgId = ipcHeader->msgId;
        transaction->serviceInfo = service;
        void* payload = NULL;
        int payloadSize = dataLen-sizeof(ipcHeader);
        if (payloadSize > 0) {
            payload = (void*)(++ipcHeader);
        }
        IPC_RESULT result = dispatchRequest(service, transaction, payload, payloadSize);
        if (result != IPC_RESULT_OK) {
            sendIpcResponse(transaction, result, NULL, 0);
        }
    }
}

int8_t allocClient(IpcServiceManager* manager, int channelId, int serviceId, int subId) {
    IpcService* service = getService(manager, serviceId);
    return IpcServiceAllocClient(service, channelId, subId);
}

void removeClient(IpcServiceManager* manager, int channelId, int serviceId, int clientId) {
    IpcService* service = getService(manager, serviceId);
    IpcServiceFreeClient(service, channelId, clientId);
}

static boolean clearClientCallback(ArrayList* list, void* itemData, void* userData) {
    IpcService* service = (IpcService*)itemData;
    int8_t channelId = *(int8_t*)userData;
    if (service) {
        IpcServiceFreeClient(service, channelId, -1);
    }
    return TRUE;
}

IPC_RESULT dispatchClientDisconnected(IpcServiceManager* manager, int8_t channelId) {
    PARAMTER_CHECK_RETURN_VALUE(manager, IPC_RESULT_INVALID_PARAMETER);
    arrayListEnum(manager->list, clearClientCallback, (void*)&channelId);
    return IPC_RESULT_OK;
}

